package ru.isys.xml.util.prepare;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Stack;
import java.util.StringTokenizer;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.TransformerException;

import org.dom4j.DocumentException;
import org.w3c.dom.Element;

import ru.isys.xml.XmlItem;
import ru.isys.xml.util.builder.ModelBuilder;
import ru.isys.xml.util.builder.OutXmlBuilder;


public class PrepareUtil
{

    /**
     * This constant may be changed from wsdl to wsdl, must be declared by user or/and putted into propetry file
     **/

    public static final String REV_NAMESPASE = "http://smev.gosuslugi.ru/rev110801";

    /**
     * This constant may be changed from wsdl to wsdl, must be declared by user or/and putted into propetry file
     **/
    public static final String XD_NAMESPASE = "http://www.w3.org/2000/09/xmldsig#";

    /**
     * This constant may be changed from wsdl to wsdl, must be declared by user or/and putted into propetry file
     **/
    //public static final String SOAP_ENV_NAMESPASE = "http://schemas.xmlsoap.org/soap/envelope/";
    public String SOAP_ENV_NAMESPASE = "";
    /**
     * Qname corresponding to REV_APPDATA_NAMESPASE
     * */
    private final QName REV_MESSAGEDATA;

    /**
     * Qname corresponding to REV_APPDATA_NAMESPASE
     * */
    private final QName REV_APPDATA;

    /**
     * Qname corresponding to XD_SIGNATURE_NAMESPASE
     * */
    private final QName XD_SIGNATURE;

    /**
     * Qname corresponding to SOAP_ENV_NAMESPASE
     * */
    private final QName SOAP_ENV;

    private final QName SOAP_BODY;

    private QName methodTag;

    /**
     * Kinda flag on open rev:appdata tag
     * */
    private boolean FIND_REV_MESSAGEDATA = false;

    private boolean FIND_XD_SIGNATURE = false;

    private boolean FIND_REV_APPDATA = false;

    /**
     * Kinda flag on open soapenv:Body tag
     * */
    private boolean FIND_SOAPBODY = false;

    private String SOAPFileURI;

    private Stack<QName> openTagsStack = new Stack<QName>();

    private ModelBuilder modelBuilder;

    public PrepareUtil(String SOAPFileURI,String soapNS, ModelBuilder modelBuilder)
    {
        this.SOAPFileURI = SOAPFileURI;
        this.modelBuilder = modelBuilder;
        this.SOAP_ENV_NAMESPASE=soapNS;
        this.SOAP_BODY = new QName(SOAP_ENV_NAMESPASE, "Body");
        SOAP_ENV = new QName(SOAP_ENV_NAMESPASE, "Envelope");
        XD_SIGNATURE = new QName(XD_NAMESPASE, "Signature");
        REV_APPDATA = new QName(REV_NAMESPASE, "AppData");
        REV_MESSAGEDATA = new QName(REV_NAMESPASE, "MessageData");
    }
    

    private XMLEventReader getReaderInstance(String SOAPFileURI) throws FileNotFoundException, XMLStreamException
    {
        XMLInputFactory inputFactory = XMLInputFactory.newInstance();
        return inputFactory.createXMLEventReader(new FileInputStream(SOAPFileURI));
    }

    public String prepareModel() throws XMLStreamException, IOException
    {
        return prepareDirtyModel();
    }

    private String prepareDirtyModel() throws XMLStreamException, IOException
    {
        Collection<String> messages = new ArrayList<String>();
        if (this.SOAPFileURI != null)
        {
            XMLEventReader reader = getReaderInstance(this.SOAPFileURI);
            try
            {
                while (reader.hasNext())
                {

                    XMLEvent event = reader.nextEvent();

                    switch (event.getEventType())
                    {
                        case XMLEvent.START_ELEMENT:
                        {

                            StartElement startElement = event.asStartElement();
                            
                            if (startElement.getName().equals(SOAP_ENV))
                            {

                            }
                            if (FIND_SOAPBODY)
                            {
                                this.methodTag = startElement.getName();
                                System.out.println(this.methodTag);
                                FIND_SOAPBODY = false;
                                writeTag(startElement, messages);
                            }

                            if (startElement.getName().equals(this.SOAP_BODY))
                            {
                                FIND_SOAPBODY = true;
                            }
                            if ((startElement.getName().equals(REV_MESSAGEDATA) || FIND_REV_MESSAGEDATA) && !FIND_XD_SIGNATURE)
                            {
                                if (startElement.getName().equals(REV_MESSAGEDATA))
                                {
                                    FIND_REV_MESSAGEDATA = true;
                                    writeTag(startElement, messages);
                                }
                                else if (startElement.getName().equals(REV_APPDATA))
                                {
                                    FIND_REV_APPDATA = true;
                                    writeTag(startElement, messages);
                                }
                                else if (startElement.getName().equals(XD_SIGNATURE))
                                {
                                    FIND_XD_SIGNATURE = true;
                                }
                                else if (FIND_REV_APPDATA)
                                {
                                    writeTag(startElement, messages);
                                }
                            }
                            break;
                        }
                        case XMLEvent.END_ELEMENT:
                        {
                            EndElement endElement = event.asEndElement();

                            if (FIND_REV_MESSAGEDATA)
                            {
                                if (FIND_XD_SIGNATURE)
                                {
                                    if (endElement.getName().equals(XD_SIGNATURE))
                                    {
                                        FIND_XD_SIGNATURE = false;
                                    }
                                }
                                else if (FIND_REV_APPDATA || endElement.getName().equals(REV_MESSAGEDATA))
                                {

                                    if (endElement.getName().equals(REV_APPDATA))
                                    {
                                        FIND_REV_APPDATA = false;
                                    }
                                    if (!openTagsStack.empty() && endElement.getName().equals(openTagsStack.peek()))
                                    {
                                        writeTag(endElement, messages);
                                    }
                                }
                            }
                            if (endElement.getName().equals(this.methodTag))
                            {
                                writeTag(endElement, messages);
                            }
                            break;
                        }
                    }
                }
                String modelXml = this.modelBuilder.transformAndShow(null);                
                return modelXml;
            }
            catch (TransformerException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            catch (DocumentException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            finally
            {
                reader.close();
            }
        }
        return null;
    }

    private void writeTag(XMLEvent element, Collection<String> log)
    {
        if (element instanceof StartElement)
        {
            StartElement startElement = element.asStartElement();
            System.out.println("    " + startElement.toString());
            log.add("    " + startElement.toString());
            createAndAddElementToModelBuilder(startElement);

            openTagsStack.push(startElement.getName());
        }
        else if (element instanceof EndElement)
        {
            EndElement endElement = element.asEndElement();
            System.out.println("    " + endElement.toString());
            log.add("    " + endElement.toString());
            openTagsStack.pop();
            bindElement(endElement.getName());
        }
    }

    private void createAndAddElementToModelBuilder(StartElement element)
    {
        QName qualifiedName = element.getName();
        Element elementToAdd = this.modelBuilder.createNode(qualifiedName.getNamespaceURI(), qualifiedName.getPrefix(), qualifiedName.getLocalPart());
        Iterator attributes = element.getAttributes();
        XmlItem item = this.modelBuilder.createXmlItem(qualifiedName,openTagsStack);
        while (attributes.hasNext())
        {            
            String nonParsedAttribute = (String) attributes.next().toString();
            StringTokenizer stringTokenizer = new StringTokenizer(nonParsedAttribute, "=");
            String attribute = stringTokenizer.nextToken();            
            this.modelBuilder.addAttributeToXmlItem(item, attribute, "");            
            elementToAdd.setAttribute(attribute, "");
        }
        this.modelBuilder.addElement(elementToAdd);
    }

    private void bindElement(QName nameToBind)
    {
        if (!nameToBind.equals(methodTag))
        {
            Element elementToBind = this.modelBuilder.getElementByName(nameToBind.getPrefix() + ":" + nameToBind.getLocalPart());
            if (!openTagsStack.empty())
            {
                QName parentQName = openTagsStack.peek();
                if (!parentQName.equals(nameToBind))
                {
                    this.modelBuilder.setFullNameXmlItem(nameToBind, parentQName);
                    Element parentElement = this.modelBuilder.getElementByName(parentQName.getPrefix() + ":" + parentQName.getLocalPart());
                    if (parentElement != null)
                    {
                        this.modelBuilder.appendChild(parentElement, elementToBind);
                    }
                    else
                    {
                        System.out.println("Cannot Find Element");
                    }
                }
            }
        }
        else
        {
            Element elementToBind = this.modelBuilder.getElementByName(nameToBind.getPrefix() + ":" + nameToBind.getLocalPart());
            this.modelBuilder.appendChildToDocument(elementToBind);
        }
    }
}
